﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Specifications;

using mvcForum.Web.ViewModels;
using mvcForum.Web.Interfaces;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Services;

namespace mvcForum.Web.Extensions {

	public static class ForumExtensions {

		/// <summary>
		/// Method for tracking the user's visit to a given forum.
		/// </summary>
		/// <param name="forum">The forum that has been visited.</param>
		public static void Track(this Forum forum) {
			// Let's get the UserProvider.
			IWebUserProvider userProvider = DependencyResolver.Current.GetService<IWebUserProvider>();
			// Are we dealing with an authenticated user?
			if (userProvider.Authenticated) {
				try {
					IContext context = DependencyResolver.Current.GetService<IContext>();
					// Let's get the user repo.
					IRepository<ForumUser> userRepository = context.GetRepository<ForumUser>();
					// Let's get the forumtrack repo.
					IRepository<ForumTrack> ftRepository = context.GetRepository<ForumTrack>();
					// Get the authenticated user.
					ForumUser user = userProvider.ActiveUser;
					//ForumUser user = userRepository.Read(userProvider.CurrentUserId);
					// Did we get an user?
					if (user != null) {
						// Let's track the user's visit to the given forum.
						ForumTrack ft = ftRepository.ReadOne(new ForumTrackSpecifications.SpecificForumAndUser(forum, user));
						// Did we get a forum track?
						if (ft == null) {
							// No, let's create one then!
							ft = new ForumTrack(forum, user);
							ftRepository.Create(ft);
						}
						// Update the Forumtrack instance (LastViewed).
						ft.Viewed();
						context.SaveChanges();
					}
				}
				catch (Exception ex) {
					// And error occured, let's log it.
					DependencyResolver.Current.GetService<ILogger>().Log(EventType.Error, "Track extension on Forum failed.", ex);
				}
			}
		}

		/// <summary>
		/// Method for determining if the user has the given access flag to the given forum.
		/// </summary>
		/// <param name="forum">The forum to test against.</param>
		/// <param name="flag">The required access level.</param>
		/// <returns>True if the user has access, otherwise false.</returns>
		public static Boolean HasAccess(this Forum forum, AccessFlag flag) {
			return (forum.GetAccess() & flag) == flag;
		}

		/// <summary>
		/// Method for getting the ForumAccess for the given forum and group.
		/// </summary>
		/// <param name="forum">The forum we need the ForumAccess for.</param>
		/// <param name="group">The group we need the ForumAccess for.</param>
		/// <returns>The ForumAccess for the given forum and group.</returns>
		public static ForumAccess GetAccess(this Forum forum, Group group) {
			// Get the ForumAccess repository.
			IRepository<ForumAccess> accessRepo = DependencyResolver.Current.GetService<IRepository<ForumAccess>>();
			// Return the ForumAccess for the given group and forum.
			return accessRepo.ReadOne(new ForumAccessSpecifications.SpecificForumAndGroup(forum, group));
		}

		/// <summary>
		/// Method for getting the AccessFlag for a given Forum.
		/// </summary>
		/// <param name="forum">The forum we need the AccessFlag for.</param>
		/// <returns>The AccessFlag for the given forum.</returns>
		public static AccessFlag GetAccess(this Forum forum) {
			// Default access is: None!
			AccessFlag output = AccessFlag.None;

			// Let's get the UserProvider.
			IWebUserProvider userProvider = DependencyResolver.Current.GetService<IWebUserProvider>();
			// Any authenticated user?
			if (userProvider.Authenticated) {
				// Let's get the groups this user belongs to and OR the accessflags!
				// First the ElementRepository.
				IRepository<ForumUser> userRepository = DependencyResolver.Current.GetService<IRepository<ForumUser>>();
				// Then the authenticated user.
				ForumUser user = userProvider.ActiveUser;
				//ForumUser user = userRepository.Read(userProvider.CurrentUserId);
				// Did we get a user?
				if (user != null) {
					IRepository<ForumAccess> accessRepository = DependencyResolver.Current.GetService<IRepository<ForumAccess>>();
					// Let's get the list of groups this user belongs to.
					foreach (var group in user.Groups()) {
						// Get the ForumAccess for the given groups!
						ForumAccess forumAccess = accessRepository.ReadOne(new ForumAccessSpecifications.SpecificForumAndGroup(forum, group));
						// Any access flag for this group/forum combination?
						if (forumAccess != null) {
							// Let's OR them to get the correct access for the user!
							output = output | forumAccess.AccessMask.AccessFlag;
						}
					}
				}
			}
			else {
				// Anonymous user!
				IRepository<Group> groupRepo = DependencyResolver.Current.GetService<IRepository<Group>>();
				// Get the guest group and return the accessflag (if any!)
				Group group = groupRepo.ReadOne(new GroupSpecifications.SpecificName("Guest"));
				// Get the ForumAccess of the forum/group combination.
				ForumAccess access = forum.GetAccess(group);
				// Did we get one?
				if (access != null) {
					// Let's return the access flag then.
					output = access.AccessMask.AccessFlag;
				}
			}

			return output;
		}

		/// <summary>
		/// Method for getting the user's last visit to a given forum.
		/// </summary>
		/// <param name="forum">The forum we need to get the last visisted timestamp for.</param>
		/// <returns>A timestamp indication when the user last visited the given forum.</returns>
		public static DateTimeOffset LastRead(this Forum forum) {
			// Let's get the UserProvider.
			IWebUserProvider userProvider = DependencyResolver.Current.GetService<IWebUserProvider>();
			// An authenticated user?
			if (userProvider.Authenticated) {
				// Let's get the ForumUser repository.
				IRepository<ForumUser> userRepository = DependencyResolver.Current.GetService<IRepository<ForumUser>>();
				// Get the authenticated user.
				ForumUser user = userProvider.ActiveUser;
				//ForumUser user = userRepository.Read(userProvider.CurrentUserId);
				// Did we get an user?
				if (user != null) {
					// Yes, let's get the ForumTrack repository.
					IRepository<ForumTrack> ftRepository = DependencyResolver.Current.GetService<IRepository<ForumTrack>>();
					// Get the ForumTrack for the given user and forum.
					ForumTrack ft = ftRepository.ReadOne(new ForumTrackSpecifications.SpecificForumAndUser(forum, user));

					// Did we get a ForumTrack? If not, return a date in the past, if we did, return the LastViewed property!
					return (ft == null ? DateTimeOffset.MinValue.ToUniversalTime() : ft.LastViewed);
				}
			}
			// Not authenticated user, return NOW, we don't track anonymous users!
			return DateTime.UtcNow;
		}

		/// <summary>
		/// Extension that returns a list of SelectListItem objects matching the given list of ForumViewModels
		/// </summary>
		/// <param name="forums">Forums to include in the returned list.</param>
		/// <returns></returns>
		public static IEnumerable<SelectListItem> ToSelectList(this IEnumerable<ForumViewModel> forums) {
			return forums.ToSelectList(null);
		}

		/// <summary>
		/// Extension that returns a list of SelectListItem objects matching the given list of ForumViewModels
		/// </summary>
		/// <param name="forums">Forums to include in the returned list.</param>
		/// <param name="selected">Id of the selected forum.</param>
		/// <returns></returns>
		public static IEnumerable<SelectListItem> ToSelectList(this IEnumerable<ForumViewModel> forums, ForumViewModel selected) {
			return forums.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name, Selected = (selected != null ? selected.Id == x.Id : false) });
		}

		/// <summary>
		/// Method for finding out whether or not the user is following a given forum.
		/// </summary>
		/// <param name="forum">The given forum.</param>
		/// <returns>True if the user is following the topic, otherwise false.</returns>
		public static Boolean Following(this Forum forum) {
			// Get the UserProvider.
			IWebUserProvider userProvider = DependencyResolver.Current.GetService<IWebUserProvider>();
			// Are we dealing with an authenticated user?
			if (userProvider.Authenticated) {
				// Yes, lets' get the ForumUser repository.
				IRepository<ForumUser> userRepository = DependencyResolver.Current.GetService<IRepository<ForumUser>>();
				// Get the user!
				ForumUser user = userProvider.ActiveUser;
				//ForumUser user = userRepository.Read(userProvider.CurrentUserId);
				// Did we get an user?
				if (user != null) {
					// Yes, let's get the FollowTopic repository.
					IRepository<FollowForum> ffRepository = DependencyResolver.Current.GetService<IRepository<FollowForum>>();
					// Get a FollowTopic instance for the user, if we get one, he's following, if not, he isn't.
					return (ffRepository.ReadOne(new FollowForumSpecifications.SpecificForumAndUser(forum, user)) != null);
				}
			}
			// Nope, not following this topic, no authenticated user found.
			return false;
		}
	}
}